Skip to content

Conversation

@sirtimid
Copy link
Contributor

@sirtimid sirtimid commented Feb 5, 2026

Summary

Add UIOrchestrator for managing visible UI vat iframes in named slots, enabling multiple caplet UIs to coexist in a structured layout.

  • Add UIOrchestrator class with slot-based iframe management
  • Add makeUIVatWorker factory for VatWorker interface
  • Add <div id="root"> mounting point to vat/iframe.html
  • Export SlotName type (currently 'main', extensible)

Usage

import { UIOrchestrator } from '@metamask/kernel-browser-runtime';

// 1. Create orchestrator with named slots
const orchestrator = UIOrchestrator.make({
  slots: { main: document.getElementById('caplet-container')! },
});

// 2. Launch a UI vat into a slot
const port = await orchestrator.launch({
  id: 'my-caplet-ui',
  uri: './vat/iframe.html',
  slot: 'main',
});

// 3. The iframe now has:
//    - lockdown() running (via endoify prelude)
//    - VatSupervisor connected via the returned port
//    - <div id="root"> for mounting Preact/React UI
//    - Console forwarding to parent

// 4. Use port for CapTP communication with the vat

// 5. Lifecycle management
orchestrator.hide('my-caplet-ui');   // Hide iframe
orchestrator.show('my-caplet-ui');   // Show iframe
orchestrator.terminate('my-caplet-ui'); // Remove iframe
orchestrator.terminateAll();         // Remove all iframes

Architecture

┌────────────────────────────────────────────────────────────────┐
│  Main UI Vat (first-party)                                     │
│  - UIVatController uses UIOrchestrator                         │
│  - Decides which caplet UIs go in which slots                  │
│  - Makes CapTP presences appear in iframes                     │
└────────────────────────────────────────────────────────────────┘
        │
        │ orchestrator.launch({ id, uri, slot: 'main' })
        ▼
┌───────────────┐  ┌───────────────┐  ┌───────────────┐
│ Caplet A UI   │  │ Caplet B UI   │  │ Caplet C UI   │
│ (vat iframe)  │  │ (vat iframe)  │  │ (vat iframe)  │
│ VatSupervisor │  │ VatSupervisor │  │ VatSupervisor │
│ + Preact UI   │  │ + Preact UI   │  │ + Preact UI   │
└───────────────┘  └───────────────┘  └───────────────┘

Test plan

  • Unit tests for UIOrchestrator (slot management, lifecycle, etc.)
  • Build passes
  • Lint passes

🤖 Generated with Claude Code


Note

Medium Risk
Introduces new iframe creation/sandboxing and cross-window messaging setup; mistakes here could affect UI isolation or messaging reliability, though the change is largely additive and covered by unit tests.

Overview
Adds a new slot-based UI iframe lifecycle manager via UIOrchestrator, which can launch sandboxed UI-vat iframes, establish a MessagePort connection, track them by id/slot, and provide show/hide/terminate APIs (including cleanup on failed loads and protection against concurrent duplicate launches).

Introduces makeUIVatWorker to expose UI vats through the existing VatWorker interface, exports the new UI surface from the package entrypoint, and updates the vat iframe template to include a #root mount point. Comprehensive unit tests are added for UIOrchestrator, and index.test.ts is updated to assert the new exports.

Written by Cursor Bugbot for commit fa096ce. This will update automatically on new commits. Configure here.

@sirtimid sirtimid requested a review from a team as a code owner February 5, 2026 16:43
@sirtimid sirtimid changed the title feat(kernel-browser-runtime): add UI vat infrastructure with slot-based orchestration feat(kernel-browser-runtime): add slot-based UIOrchestrator Feb 5, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Feb 5, 2026

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 78.39%
⬆️ +0.15%
6246 / 7967
🔵 Statements 78.37%
⬆️ +0.15%
6345 / 8096
🔵 Functions 76.67%
⬆️ +0.11%
1591 / 2075
🔵 Branches 78.22%
⬇️ -0.10%
2263 / 2893
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/kernel-browser-runtime/src/index.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/kernel-browser-runtime/src/ui/UIOrchestrator.ts 97.89% 88.46% 95.83% 97.89% 436, 441
packages/kernel-browser-runtime/src/ui/index.ts 100% 100% 100% 100%
packages/kernel-browser-runtime/src/ui/makeUIVatWorker.ts 10% 0% 0% 10% 68-91
Generated in workflow #3607 for commit fa096ce by the Vitest Coverage Report Action

title?: string,
visible = true,
): HTMLIFrameElement {
const iframe = document.createElement('iframe');
Copy link
Contributor Author

@sirtimid sirtimid Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use createWindow from @metamask/snaps-utils? But UIOrchestrator needs the iframe element itself for lifecycle management

sirtimid and others added 4 commits February 9, 2026 16:17
…ed orchestration

Add UIOrchestrator for managing visible UI vat iframes in named slots,
enabling multiple caplet UIs to coexist in a structured layout.

- Add UIOrchestrator class with slot-based iframe management
- Add makeUIVatWorker factory for VatWorker interface
- Add <div id="root"> mounting point to vat/iframe.html
- Export SlotName type (currently 'main', extensible)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create logger once at construction time in makeUIVatWorker instead of
  per method call
- Track in-progress launches to prevent concurrent launches with same ID
  from orphaning iframes
- Clean up iframe from DOM when connection establishment fails

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ion error handling

- Save original document.createElement before mocking to prevent infinite
  recursion when creating non-iframe elements in tests
- Move iframe creation inside try block so ID is removed from
  launchesInProgress if createIframe throws

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…rminateAll

Use Array.from() to snapshot uiVats keys before iterating, consistent
with the pattern in PlatformServicesServer.ts. Prevents potential issues
from map modification during iteration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@sirtimid sirtimid force-pushed the sirtimid/ui-vat-infrastructure branch from eec21e3 to 4daeb57 Compare February 9, 2026 15:18
…ent launch prevention

Cover the iframe load error cleanup path (remove iframe, clear
launchesInProgress, allow retry) and the concurrent launch guard
that rejects duplicate IDs while a launch is in progress.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

*/
getSlotNames(): SlotName[] {
return Object.keys(this.#slots) as SlotName[];
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsafe type cast in getSlotNames returns misleading type

Low Severity

The getSlotNames() method uses an unsafe cast as SlotName[] which creates a type mismatch when the orchestrator is configured with extra slots beyond 'main'. The slots object can accept additional properties like 'sidebar' (as demonstrated in the test at line 627-628), but Object.keys() returns all keys while the return type claims it's only ('main')[]. This type lie could mislead consumers who rely on TypeScript to understand the possible return values. The PR description mentions SlotName is "extensible", but the current type design doesn't support extension without unsafe casting.

Fix in Cursor Fix in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant